package com.chenfan.ccp.plug.log.config;
import com.chenfan.ccp.entity.BaseUserVO;
import com.chenfan.ccp.executor.SmartExecutorService;
import com.chenfan.ccp.plug.log.util.TraceIdTaskDecorator;
import com.chenfan.infra.constants.CommonBeanConstants;
import com.chenfan.ccp.common.listener.AbstractEventListener;
import com.chenfan.ccp.plug.business.parser.UserIdParser;
import com.chenfan.ccp.plug.log.handler.RequestParameterInterceptor;
import com.chenfan.ccp.plug.log.handler.ResponseParameterInterceptor;
import com.chenfan.ccp.plug.log.trace.TraceLog;
import com.chenfan.ccp.util.LogUtil;
import com.chenfan.ccp.util.start.ApplicationContextUtil;
import com.chenfan.ccp.util.tool.PushUtil;
import com.chenfan.ccp.utils.TraceIdUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.util.StopWatch;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static com.chenfan.ccp.plug.business.constants.Constants.USER_ID;

/**
 * filter拦截器
 */
public class NativeServletFilter {
    private static final Logger logger = LoggerFactory.getLogger("HTTP_LOG");


    private static void write(HttpServletResponse response, byte[] msg) throws IOException {
        if (response.isCommitted()
                || !(response instanceof ResponseParameterInterceptor)
                || (response.getContentType() != null
                && !response.getContentType()
                .toLowerCase().startsWith(MediaType.APPLICATION_JSON_VALUE))) {

            return;
        }

        response.reset();
        ServletOutputStream outputStream = response.getOutputStream();
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        outputStream.write(msg);
        outputStream.flush();
        outputStream.close();
    }


    /**
     * 定制过滤器
     */
    public static class CustomFilter extends AbstractEventListener implements Filter {

        private final String CLASSNAME = CustomFilter.class.getSimpleName();
        private final TraceLog traceLog = new TraceLog();
        protected Map<String, String> allUrlLine = null;
        @Autowired
        private LogUrlConfig logUrlConfig;
        private UserIdParser userIdParser;

        private int httpAlarmSeconds = 30; //httpalert TODO: 2023-02-17

        private final ExecutorService executorService = SmartExecutorService.newFixedThreadPool(Runtime.getRuntime().availableProcessors()*2);

        {
            Runtime.getRuntime().addShutdownHook(new Thread(() -> executorService.shutdown()));
        }

        public CustomFilter(UserIdParser userIdParser) {
            this.userIdParser = userIdParser;
        }

        @Override
        public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {

            if (!ApplicationContextUtil.isConsoleStart()) {
                return;
            }

            Thread thread = new Thread(() -> {
                try {
                    TimeUnit.SECONDS.sleep(10);
                    this.allUrlLine = Collections.unmodifiableMap(this.logUrlConfig.getAllUrlFile());
                } catch (IOException | InterruptedException var2) {
                }
            });
            thread.setDaemon(true);
            thread.start();

        }

        /**
         * 获取参数的字符串形式
         *
         * @param map
         * @return
         */
        private String getParamString(Map<String, String[]> map) {
            StringBuilder sb = new StringBuilder();
            for (Map.Entry<String, String[]> e : map.entrySet()) {
                sb.append(e.getKey()).append("=");
                String[] value = e.getValue();
                if (value != null && value.length == 1) {
                    sb.append(value[0]).append("&");
                } else {
                    sb.append(Arrays.toString(value)).append("&");
                }
            }
            if (sb.length() >= 1) {
                if (sb.substring(sb.length() - 1, sb.length()).equals("&")) {
                    sb.deleteCharAt(sb.length() - 1);
                }
            }
            return sb.toString();
        }

        /**
         * 解析userId
         *
         * @param request
         * @param response
         * @return
         */
        private BaseUserVO parseUserId(HttpServletRequest request, HttpServletResponse response) {
            return userIdParser.parserUserId(request, response);
        }

        /**
         * 封装请求
         *
         * @param request
         * @param response
         * @param chain
         * @param userVO
         * @throws IOException
         */
        private void traceHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain, BaseUserVO userVO) throws IOException {
            RequestParameterInterceptor newRequest = new RequestParameterInterceptor(request, false);
            ResponseParameterInterceptor newResponse = new ResponseParameterInterceptor(response);
            String requstURI = request.getRequestURI();
            TraceIdUtil.logMDC(newRequest, newResponse, TraceIdUtil.SHORT_TRACE_ID,TraceIdUtil.getUUID());
            TraceIdUtil.logMDC(newRequest, newResponse, CommonBeanConstants.TENANT_ID, Objects.isNull(userVO) || Objects.isNull(userVO.getTenantId()) ? "" : "" + userVO.getTenantId());
            TraceIdUtil.logMDC(newRequest, newResponse, CommonBeanConstants.USER_ID, Objects.isNull(userVO) || Objects.isNull(userVO.getUserId()) ? "" : "" + userVO.getUserId());
            StopWatch timeWatch = new StopWatch();
            timeWatch.start();
            try {
                USER_ID.set(userVO);
                chain.doFilter(newRequest, newResponse);
            } catch (Throwable e) {
                String traceId = TraceIdUtil.getTraceId();
                // 增加sql
                if (e instanceof SQLException) {
                    traceLog.log(CLASSNAME, "服务={} traceId={} 方法={} sql 异常", ApplicationContextUtil.getServiceName(), traceId, request.getRequestURI(), e);
                    // 降级操作 // TODO: 2023-02-16
                    // sql 告警操作
                    timeWatch.stop();
                    final long totalTimeMillis = timeWatch.getTotalTimeMillis();
                    executorService.execute(new TraceIdTaskDecorator().decorate(() -> PushUtil.getInstance().send(PushUtil.PushType.DINGDING, "", LogUtil.getHttpMsg("sql异常告警",requstURI,totalTimeMillis))));

                } else {
                    traceLog.log(CLASSNAME, "服务={},traceId={},方法={} 全局异常", ApplicationContextUtil.getServiceName(), traceId, request.getRequestURI(), e);
                }
            } finally {
                // 自定义封装返回结果
                write(newResponse, newResponse.toByteArray());
                if (allUrlLine != null && ApplicationContextUtil.isConsoleStart()) {
                    String filePath = allUrlLine.get(request.getRequestURI());
                    if (filePath != null && !filePath.isEmpty()) {
                        logger.info("RequestFile:       " + filePath);
                    }
                }
                String requestParams = getParamString(newRequest.getParameterMap());
                timeWatch.stop();
                final long totalTimeMillis = timeWatch.getTotalTimeMillis();
                String longTimeStr = (totalTimeMillis >= httpAlarmSeconds * 1000)? "请求超时长" : "";
                logger.info("RequestURI====={};RequestParams====={};Total====={}ms;"+longTimeStr, request.getRequestURI(), requestParams,totalTimeMillis);
                if (totalTimeMillis >= httpAlarmSeconds * 1000 && !"chenfan-cloud-litchi".equals(ApplicationContextUtil.getServiceName())){
                    executorService.execute(new TraceIdTaskDecorator().decorate(() -> PushUtil.getInstance().send(PushUtil.PushType.DINGDING, "", LogUtil.getHttpMsg("请求时长告警",requstURI, totalTimeMillis))));
                }
            }
        }

        @Override
        public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) res;
            // 解析用户Id
            BaseUserVO userVO = parseUserId(request, response);
            // 跟踪请求
            traceHttp(request, response, chain, userVO);
        }

        @Override
        public void destroy() {
            // filter消失时  清空相关数据
            TraceIdUtil.clearTrace();
            USER_ID.remove();
        }

        @Override
        public Object getListener() {
            return this;
        }
    }

}
